//
// Copyright (c) 2009 All Right Reserved
//
// vl
//
// 2009-01-01
// Contains ...
using System;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using JetBrains.Annotations;
namespace LargoCommon.Midi
{
///
/// Midi Internal.
///
internal static class MidiInternalMessages {
///
/// Synchronization Lock.
///
private static readonly object ThisLock = new object();
///
/// Gets a value indicating whether this instance is open.
///
///
/// True if this instance is open; otherwise, false.
///
private static bool IsOpen => !string.IsNullOrEmpty(PlayingAlias);
///
/// Gets or sets the playing alias.
///
///
/// The playing alias.
///
private static string PlayingAlias { get; set; }
#region Midi Output Messages - sending
///
/// Send Midi Message.
///
/// Midi Command Code.
/// Midi Channel.
/// Data Value1.
/// Data Value2.
public static void SendMidiMessage(MidiCommandCode givenType, int channel, int dataValue1, int dataValue2 = 0) {
lock (ThisLock) {
if (MidiInternalDevices.MidiDeviceHandle == null) {
MidiInternalDevices.InternalOpenMidi();
}
if (MidiInternalDevices.MidiDeviceHandle != null) {
SendMidiMessage(MidiInternalDevices.MidiDeviceHandle, ((int)givenType | channel) | (dataValue1 << 8) | (dataValue2 << 16));
}
}
}
#endregion
#region SendString
///
/// Prepares the midi.
///
public static void PrepareMidi() {
//// We can't play using MCI if we already have an open handle to the default
//// MIDI device. As such, we'll temporarily close it if its open and then
//// when we're done reopen it if it was open.
if (IsOpen) {
MidiFileClose();
}
}
///
/// Midi FileOpen.
///
/// Midi file Path.
/// Midi file Alias.
public static void MidiFileOpen(string path, string alias) {
CheckPath(path);
PlayingAlias = alias;
//// MidiInternalDevices.PrepareMidi();
//// Open the file, play it, close it
MciSendString(string.Format(CultureInfo.InvariantCulture, "open \"{0}\" type mpegvideo alias {1}", path, alias));
}
///
/// Midi FilePlay.
///
public static void MidiFilePlay() {
lock (ThisLock) {
//// MidiInternalDevices.MciSendString("play " + alias + " wait");
if (IsOpen) {
MciSendString("play " + PlayingAlias);
}
}
}
///
/// Midi FileClose.
///
public static void MidiFileClose() {
if (!IsOpen) {
return;
}
MciSendString("close " + PlayingAlias);
PlayingAlias = null;
//// Reopen the MIDI device if it was previously open
//// if (MidiInternalDevices.IsOpen) { MidiInternalDevices.InternalOpenMidi(); }
}
/// Plays the specified MIDI file using Media Control Interface (MCI).
/// The MIDI file to be played.
[UsedImplicitly]
public static void PlayFile(string path) {
CheckPath(path);
//// Play the file using interop calls: open the file, play it (wait for it to finish), close it
var alias = Guid.NewGuid().ToString("N", CultureInfo.CurrentCulture); //// randomly generated alias to avoid collisions
lock (ThisLock) {
//// We can't play using MCI if we already have an open handle to the default
//// MIDI device. As such, we'll temporarily close it if its open and then
//// when we're done reopen it if it was open.
if (MidiInternalDevices.IsOpen) {
MidiInternalDevices.InternalCloseMidi();
}
//// Open the file, play it, close it
MciSendString(string.Format(CultureInfo.InvariantCulture, "open \"{0}\" type mpegvideo alias {1}", path, alias));
MciSendString(string.Format(CultureInfo.InvariantCulture, "play {0} wait", alias));
MciSendString("close " + alias);
//// Reopen the MIDI device if it was previously open
//// if (MidiInternalDevices.IsOpen) { MidiInternalDevices.InternalOpenMidi(); }
}
}
#endregion
#region MCI Commands
/// Sends an MCI command.
/// The command to be sent.
public static void MciSendString(string command) {
// Make sure we got a command
if (command == null) {
throw new ArgumentNullException(nameof(command));
}
// Send the command.
var rv = NativeMethods.MciSendString(command, null, 0, IntPtr.Zero);
if (rv != 0) {
MidiInternalDevices.ThrowMciError(rv, string.Format(CultureInfo.InvariantCulture, "Could not execute command '{0}'.", command));
}
}
#endregion
#region
///
/// Check Path.
///
/// Midi File Path.
private static void CheckPath(string path) {
// Validate the parameter; make sure the file actually exists
if (path == null) {
throw new ArgumentNullException(nameof(path));
}
if (!File.Exists(path)) {
throw new FileNotFoundException("The MIDI file was not found.", path);
}
}
#endregion
/// Sends the message to as a short MIDI message to the MIDI output device.
/// Handle to the MIDI output device.
/// The message to be sent.
[HandleProcessCorruptedStateExceptions]
[SecurityCritical]
private static void SendMidiMessage(MidiDeviceHandle handle, int message) {
Contract.Requires(handle != null);
lock (ThisLock) {
if (handle == null) {
MidiInternalDevices.InternalOpenMidi();
}
//// if (handle == null) {
//// throw new ArgumentNullException("handle", "The handle does not exist. Make sure the MIDI device has been opened."); }
//// if (handle == null) { return; }
//// 2013/02
if (handle != null)
{
var h = handle.Handle;
if (h == 0) { //// !handle.IsOpen
return;
}
//// An unhandled exception of type 'System.AccessViolationException' occurred in LargoBase.dll
//// int result = 0;
//// try {
//// Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
//// When handle.Handle > 2 mil !?!
//// result =
NativeMethods.MidiOutShortMessage(h, message); //// MidiError result
}
//// }
//// catch (AccessViolationException) { return; }
//// 2013/02 The handler is used by another process, such as a callback.
//// if (result != 0) { //// MidiError.MidiSystemErrorNOERROR
//// MidiInternalDevices.ThrowMciError(result, string.Format(CultureInfo.InvariantCulture, "Could not execute message '{0}'.", message));
//// throw new Exception("Could not send MIDI message: " + Result.ToString(CultureInfo.CurrentCulture)); }
}
}
///
/// Native Methods.
///
private static class NativeMethods {
#region Native Methods - Low-Level MIDI API - Messages
/// The function sends a short MIDI message to the specified MIDI output device.
/// Handle to the MIDI output device.
/// MIDI message.
/// Returns SystemNoError if successful or an error otherwise.
[DllImport("winmm.dll", EntryPoint = "midiOutShortMsg", CharSet = CharSet.Ansi)] //// CharSet.Ansi
[SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable", MessageId = "return", Justification = "No other result found.")]
public static extern int MidiOutShortMessage(int hMidiOut, int dwMsg);
//// [DllImport("winmm.dll")]
//// protected static extern int midiOutShortMsg(int handle, int message);
//// Commented out as Unused
//// [DllImport("winmm.dll")]
//// protected static extern int midiOutPrepareHeader(int handle, IntPtr headerPtr, int sizeOfMidiHeader);
//// Commented out as Unused
//// [DllImport("winmm.dll")]
//// protected static extern int midiOutUnprepareHeader(int handle, IntPtr headerPtr, int sizeOfMidiHeader);
//// Commented out as Unused
//// [DllImport("winmm.dll")]
//// protected static extern int midiOutLongMsg(int handle, IntPtr headerPtr, int sizeOfMidiHeader);
#endregion
#region DllImports for MCI
///
/// The mciSendString function sends a command string to an MCI device. The device that the
/// command is sent to is specified in the command string.
///
/// Pointer to a null-terminated string that specifies an MCI command string.
/// Pointer to a buffer that receives return information. If no return information is needed, this parameter can be null.
/// Size, in characters, of the return buffer specified by the previous parameter.
/// Handle to a callback window if the "notify" flag was specified in the command string.
///
/// Returns zero if successful or an error otherwise. The low-order word of the returned
/// DWORD value contains the error return value. If the error is device-specific, the
/// high-order word of the return value is the driver identifier; otherwise, the high-order
/// word is zero.
///
//// Use string (or System.String) for a const char*, but StringBuilder for a char*.
[DllImport("winmm.dll", EntryPoint = "mciSendStringA", CharSet = CharSet.Ansi)] //// CharSet.Ansi //// 11/2010 - MarshalAs
public static extern int MciSendString(string lpszCommand, StringBuilder lpszReturn, int cchReturn, IntPtr callbackHandle);
//// [DllImport("winmm.dll", EntryPoint = "mciSendStringA", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
//// public static extern int MciSendString([MarshalAs(UnmanagedType.LPWStr)]string lpszCommand, [MarshalAs(UnmanagedType.LPWStr)]StringBuilder lpszReturnString, int cchReturn, IntPtr callbackHandle);
//// public static extern int MciSendString([MarshalAs(UnmanagedType.LPWStr)]string lpszCommand, [MarshalAs(UnmanagedType.LPWStr)]StringBuilder lpszReturnString, int cchReturn, IntPtr callbackHandle);
//// [DllImport("winmm.dll", CharSet = CharSet.Ansi, BestFitMapping = true, ThrowOnUnmappableChar = true)]
//// [return: MarshalAs(UnmanagedType.U4)]
//// public static extern uint mciSendCommand(uint mciId, uint uMsg, uint dwParam1, IntPtr dwParam2);
////
//// Sends command string.
////
//// Command string.
//// Return string.
//// Return Length.
//// Callback Handle.
//// Returns value.
//// [DllImport("winmm.dll")]
//// Use string (or System.String) for a const char*, but StringBuilder for a char*.
//// private static extern long mciSendString([MarshalAs(UnmanagedType.LPWStr)]string commandString, [MarshalAs(UnmanagedType.LPWStr)]StringBuilder returnString, int iReturnLength, IntPtr callbackHandle);
//// public static extern long mciSendString(string commandString, StringBuilder returnString, int iReturnLength, IntPtr callbackHandle);
#endregion
}
}
}